'use strict'; const { Document } = require('/document.js'); const { AddChildNodesCommandBuilder, NodeChildType, InsertionMode, DocumentCommand } = require('/commands.js'); const { Dialog, DialogResult } = require('/dialog.js'); const { UnitType } = require('/units.js'); const { app } = require('/application.js'); const { PinchPunchFilterRasterNodeDefinition, TwirlFilterRasterNodeDefinition, RippleFilterRasterNodeDefinition, SphericalFilterRasterNodeDefinition, RadialBlurFilterRasterNodeDefinition, MotionBlurFilterRasterNodeDefinition, PixelateFilterRasterNodeDefinition, DiffuseFilterRasterNodeDefinition, } = require('/nodes.js'); function abort(msg, alert = true) { if (alert) app.alert(msg); throw new Error(msg); } function isOk(r) { return (r?.value ?? r) === DialogResult.Ok.value; } const doc = Document.current; if (!doc) abort('No document open.'); const spread = doc.currentSpread; // Find target layer BEFORE the dialog: selected node or first layer let targetLayer = null; for (const node of doc.selection.nodes) { targetLayer = node; break; } if (!targetLayer) targetLayer = spread.firstChild; if (!targetLayer) abort('No layer found.'); const layerName = targetLayer.userDescription || targetLayer.defaultDescription || 'unnamed'; const { x, y, width, height } = targetLayer.getSpreadBaseBox(false); const minDim = Math.min(width, height); // ---------------- Dialog (3 columns, 1 group each) ---------------- const N = UnitType.Number; const dlg = Dialog.create('Random Distortion Effects'); // Column 1: general + Pinch/Punch + Twirl const g1 = dlg.addColumn().addGroup('General / Pinch-Punch / Twirl'); const edtCount = g1.addUnitValueEditor('Number of effects', N, N, 20, 1, 500).setPrecision(0); const usePinch = g1.addCheckBox('Use Pinch/Punch', true); const pinIntMin = g1.addUnitValueEditor('Pinch intensity min', N, N, -100, -100, 100).setPrecision(0); const pinIntMax = g1.addUnitValueEditor('Pinch intensity max', N, N, 100, -100, 100).setPrecision(0); const pinRadMin = g1.addUnitValueEditor('Pinch radius min (% of layer)', N, N, 5, 1, 100).setPrecision(0); const pinRadMax = g1.addUnitValueEditor('Pinch radius max (% of layer)', N, N, 35, 1, 100).setPrecision(0); const useTwirl = g1.addCheckBox('Use Twirl', true); const twiAngMin = g1.addUnitValueEditor('Twirl angle min (°)', N, N, -50, -720, 720).setPrecision(0); const twiAngMax = g1.addUnitValueEditor('Twirl angle max (°)', N, N, 50, -720, 720).setPrecision(0); const twiRadMin = g1.addUnitValueEditor('Twirl radius min (% of layer)', N, N, 2, 1, 100).setPrecision(0); const twiRadMax = g1.addUnitValueEditor('Twirl radius max (% of layer)', N, N, 10, 1, 100).setPrecision(0); // Column 2: Ripple + Spherical + Radial blur const g2 = dlg.addColumn().addGroup('Ripple / Spherical / Radial Blur'); const useRipple = g2.addCheckBox('Use Ripple', true); const ripIntMin = g2.addUnitValueEditor('Ripple intensity min', N, N, 5, 0, 500).setPrecision(0); const ripIntMax = g2.addUnitValueEditor('Ripple intensity max', N, N, 20, 0, 500).setPrecision(0); const useSph = g2.addCheckBox('Use Spherical', true); const sphIntMin = g2.addUnitValueEditor('Spherical intensity min', N, N, -1, -1, 1).setPrecision(2); const sphIntMax = g2.addUnitValueEditor('Spherical intensity max', N, N, 1, -1, 1).setPrecision(2); const sphRadMin = g2.addUnitValueEditor('Spherical radius min (% of layer)', N, N, 5, 1, 100).setPrecision(0); const sphRadMax = g2.addUnitValueEditor('Spherical radius max (% of layer)', N, N, 20, 1, 100).setPrecision(0); const useRadial = g2.addCheckBox('Use Radial Blur', true); const radAngMin = g2.addUnitValueEditor('Radial blur angle min (°)', N, N, 0, 0, 180).setPrecision(1); const radAngMax = g2.addUnitValueEditor('Radial blur angle max (°)', N, N, 10, 0, 180).setPrecision(1); // Column 3: Motion blur + Pixelate + Diffuse const g3 = dlg.addColumn().addGroup('Motion Blur / Pixelate / Diffuse'); const useMotion = g3.addCheckBox('Use Motion Blur', true); const motRadMin = g3.addUnitValueEditor('Motion blur radius min (px)', N, N, 5, 0, 1024).setPrecision(0); const motRadMax = g3.addUnitValueEditor('Motion blur radius max (px)', N, N, 20, 0, 1024).setPrecision(0); const motAngMin = g3.addUnitValueEditor('Motion blur angle min (°)', N, N, 0, 0, 360).setPrecision(0); const motAngMax = g3.addUnitValueEditor('Motion blur angle max (°)', N, N, 360, 0, 360).setPrecision(0); const usePixel = g3.addCheckBox('Use Pixelate', true); const pixMin = g3.addUnitValueEditor('Pixelate size min', N, N, 5, 0, 1000).setPrecision(0); const pixMax = g3.addUnitValueEditor('Pixelate size max', N, N, 20, 0, 1000).setPrecision(0); const useDiffuse = g3.addCheckBox('Use Diffuse', true); const difMin = g3.addUnitValueEditor('Diffuse intensity min', N, N, 0.1, 0, 1).setPrecision(2); const difMax = g3.addUnitValueEditor('Diffuse intensity max', N, N, 0.2, 0, 1).setPrecision(2); dlg.initialWidth = 900; const result = dlg.runModal(); if (!isOk(result)) abort('Cancelled.', false); // runModal resets the spread context, so set it again AFTER the dialog closes doc.executeCommand(DocumentCommand.createSetCurrentSpread(spread)); // ---------------- Read values ---------------- // Returns [lo, hi] clamped to the legal range, swapping if min > max function range(minCtrl, maxCtrl, lo, hi) { let a = Math.max(lo, Math.min(hi, minCtrl.value)); let b = Math.max(lo, Math.min(hi, maxCtrl.value)); if (a > b) { const t = a; a = b; b = t; } return [a, b]; } const DEG = Math.PI / 180; const NUM_EFFECTS = Math.max(1, Math.round(edtCount.value)); const rPinInt = range(pinIntMin, pinIntMax, -100, 100); const rPinRad = range(pinRadMin, pinRadMax, 1, 100); const rTwiAng = range(twiAngMin, twiAngMax, -720, 720); const rTwiRad = range(twiRadMin, twiRadMax, 1, 100); const rRipInt = range(ripIntMin, ripIntMax, 0, 500); const rSphInt = range(sphIntMin, sphIntMax, -1, 1); const rSphRad = range(sphRadMin, sphRadMax, 1, 100); const rRadAng = range(radAngMin, radAngMax, 0, 180); const rMotRad = range(motRadMin, motRadMax, 0, 1024); const rMotAng = range(motAngMin, motAngMax, 0, 360); const rPix = range(pixMin, pixMax, 0, 1000); const rDif = range(difMin, difMax, 0, 1); // ---------------- Helpers ---------------- function rnd(r) { return r[0] + Math.random() * (r[1] - r[0]); } function rndPos() { return { x: x + (-0.1 * width + Math.random() * 1.2 * width), y: y + (-0.1 * height + Math.random() * 1.2 * height) }; } // % of the layer's smaller side -> pixels (filters cap radius at 1024) function rndRadius(pctRange) { return Math.min(1024, Math.max(10, minDim * rnd(pctRange) / 100)); } // Filter definitions need a typed parameters object, not a plain {} function setParams(def, values) { const p = def.parameters; for (const k of Object.keys(values)) p[k] = values[k]; def.parameters = p; } // ---------------- Factories (only the ticked ones) ---------------- const factories = []; if (usePinch.value) factories.push(() => { const def = PinchPunchFilterRasterNodeDefinition.createDefault(doc); const pos = rndPos(); setParams(def, { intensity: rnd(rPinInt), radius: rndRadius(rPinRad), position: pos }); return { def, type: 'PinchPunch', pos }; }); if (useTwirl.value) factories.push(() => { const def = TwirlFilterRasterNodeDefinition.createDefault(doc); const pos = rndPos(); setParams(def, { angle: rnd(rTwiAng) * DEG, radius: rndRadius(rTwiRad), position: pos }); return { def, type: 'Twirl', pos }; }); if (useRipple.value) factories.push(() => { const def = RippleFilterRasterNodeDefinition.createDefault(doc); const pos = rndPos(); setParams(def, { intensity: rnd(rRipInt), position: pos }); return { def, type: 'Ripple', pos }; }); if (useSph.value) factories.push(() => { const def = SphericalFilterRasterNodeDefinition.createDefault(doc); const pos = rndPos(); setParams(def, { intensity: rnd(rSphInt), radius: rndRadius(rSphRad), position: pos }); return { def, type: 'Spherical', pos }; }); if (useRadial.value) factories.push(() => { const def = RadialBlurFilterRasterNodeDefinition.createDefault(doc); const pos = rndPos(); setParams(def, { angle: rnd(rRadAng) * DEG, position: pos }); return { def, type: 'RadialBlur', pos }; }); if (useMotion.value) factories.push(() => { const def = MotionBlurFilterRasterNodeDefinition.createDefault(); setParams(def, { radius: rnd(rMotRad), angle: rnd(rMotAng) * DEG }); return { def, type: 'MotionBlur', pos: null }; }); if (usePixel.value) factories.push(() => { const def = PixelateFilterRasterNodeDefinition.createDefault(); setParams(def, { quantisation: rnd(rPix) }); return { def, type: 'Pixelate', pos: null }; }); if (useDiffuse.value) factories.push(() => { const def = DiffuseFilterRasterNodeDefinition.createDefault(); setParams(def, { intensity: rnd(rDif) }); return { def, type: 'Diffuse', pos: null }; }); if (factories.length === 0) abort('No effect types are ticked. Tick at least one.'); console.log(`Applying ${NUM_EFFECTS} effects from ${factories.length} enabled type(s) to layer: ${layerName}`); console.log(`Layer bounds: x=${x.toFixed(0)}, y=${y.toFixed(0)}, w=${width.toFixed(0)}, h=${height.toFixed(0)}`); // ---------------- Build + apply ---------------- const builder = AddChildNodesCommandBuilder.create(); builder.setInsertionTarget(targetLayer); builder.setInsertionMode(InsertionMode.InsertAsFirstChild); builder.clearCurrentSelection = false; builder.appendCurrentSelection = false; const addFns = { PinchPunch: 'addPinchPunchFilterRasterNode', Twirl: 'addTwirlFilterRasterNode', Ripple: 'addRippleFilterRasterNode', Spherical: 'addSphericalFilterRasterNode', RadialBlur: 'addRadialBlurFilterRasterNode', MotionBlur: 'addMotionBlurFilterRasterNode', Pixelate: 'addPixelateFilterRasterNode', Diffuse: 'addDiffuseFilterRasterNode', }; const counts = {}; for (let i = 0; i < NUM_EFFECTS; i++) { const factory = factories[Math.floor(Math.random() * factories.length)]; const { def, type } = factory(); builder[addFns[type]](def); counts[type] = (counts[type] || 0) + 1; } const cmd = builder.createCommand(false, NodeChildType.Enclosure); doc.executeCommand(cmd); console.log(`Done! ${NUM_EFFECTS} effects applied ->`, JSON.stringify(counts));